﻿using System;
using System.Xml;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.WebSockets;
using System.Security.Cryptography;
using PdfSharp.Pdf;
using PdfSharp.Drawing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TrinityAPI.Base;
using TrinityAPI.Base.Tools;
using TrinityAPI.Base.Models;

namespace TrinityAPI.O2O.eCommerce
{
    public class GetWaybillInfo : IAction
    {
        public JToken Invoke(JObject dataset, params JToken[] args)
        {
            try
            {
                var request = dataset.GetJsonObject("request");
                var constant = dataset.GetJsonObject("constant");
                var jdeShops = constant.GetJsonObjectList<string>("jdeShops");
                var connection = constant.GetJsonObject("connection");
                var o2o = connection.GetJsonObject("o2o");
                var oms = connection.GetJsonObject("oms");

                string courierCompCode = "SF";
                string xml = request.GetJsonObjectStringValue("xml");
                string verifyCode = request.GetJsonObjectStringValue("verifyCode");

                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                string json = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None);
                var requestObject = JObject.Parse(json).GetJsonObject("Request");

                string apiKey = requestObject.GetJsonObjectStringValue("Head");
                string service = requestObject.GetJsonObjectStringValue("@service");

                #region 验证
                string secretKey = null;
                if (!string.IsNullOrWhiteSpace(apiKey))
                {
                    string sql = "SELECT SerectKey FROM API_APIKey WHERE APICode='O2OAPI' AND APIKey=@APIKey AND Disabled=0";
                    var dbBase = Utils.GetDBAdapter("oms", oms, null, false);
                    var obj = dbBase.ExecuteScalar(sql, new List<DBParameter>() { new DBParameter("@APIKey", apiKey) }, System.Data.CommandType.Text);
                    secretKey = obj?.ToString();
                }

                if (string.IsNullOrWhiteSpace(secretKey) || MD5ToBase64String(xml + secretKey) != verifyCode)
                    throw new BaseException("@E000000010", "");
                #endregion

                string orderId = null;
                JObject bodyObject = null;
                if (service == "OrderService")
                {
                    bodyObject = requestObject.GetJsonObject("Body").GetJsonObject("Order");
                    orderId = bodyObject.GetJsonObjectStringValue("@orderid");
                }
                else if (service == "OrderSearchService")
                {
                    bodyObject = requestObject.GetJsonObject("Body").GetJsonObject("OrderSearch");
                    orderId = bodyObject.GetJsonObjectStringValue("@orderid");
                }
                else
                {
                    throw new BaseException("@E120000002", "");
                }

                if (string.IsNullOrWhiteSpace(orderId))
                    throw new BaseException("@E120000003", "");

                string SONo = null;
                string eSONo = null;
                string ePlatform = null;
                string eShopCode = null;
                string sql1 = "SELECT SONo,eSONo,ePlatform,eShopCode FROM O2O_SOFulfillPlanH WHERE SubSONo=@OrderId";
                var dbBase1 = Utils.GetDBAdapter("o2o", o2o, null, false);
                var ds = dbBase1.Query(sql1, new List<DBParameter>() { new DBParameter("@OrderId", orderId) }, System.Data.CommandType.Text);
                if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
                {
                    var fulfillOrders = JSONHelper.DataTableToJArray(ds.Tables[0]);
                    if (fulfillOrders != null && fulfillOrders.Count == 1)
                    {
                        var fulfillOrder = fulfillOrders[0] as JObject;
                        SONo = fulfillOrder.GetJsonObjectStringValue("SONo");
                        eSONo = fulfillOrder.GetJsonObjectStringValue("eSONo");
                        ePlatform = fulfillOrder.GetJsonObjectStringValue("ePlatform");
                        eShopCode = fulfillOrder.GetJsonObjectStringValue("eShopCode");
                    }
                }
                else
                {
                    throw new BaseException("@E120000004", "");
                }

                if (string.IsNullOrWhiteSpace(SONo) || string.IsNullOrWhiteSpace(eSONo) || string.IsNullOrWhiteSpace(ePlatform) || string.IsNullOrWhiteSpace(eShopCode))
                    throw new BaseException("@E120000005", "");

                if (jdeShops != null && (jdeShops.Contains(eShopCode.ToLower()) || jdeShops.Contains(ePlatform.ToLower())))
                {
                    if (service == "OrderSearchService")
                        throw new Exception();

                    var jdWaybillConfig = constant.GetNecessaryJsonObject("jdWaybillConfig", "constant");
                    var jdCourierInfo = constant.GetNecessaryJsonObject("jdCourierInfo", "constant");
                    var courierInfoConfig = jdCourierInfo.GetNecessaryJsonObject(courierCompCode, "constant.jdCourierInfo");

                    string path = "constant.jdWaybillConfig";
                    string waybillFileDir = jdWaybillConfig.GetNecessaryJsonObjectStringValue("waybillfiledir", path);
                    string printerService = jdWaybillConfig.GetNecessaryJsonObjectStringValue("printerservice", path);
                    string url = jdWaybillConfig.GetNecessaryJsonObjectStringValue("url", path);
                    string appkey = jdWaybillConfig.GetNecessaryJsonObjectStringValue("appkey", path);
                    string secret = jdWaybillConfig.GetNecessaryJsonObjectStringValue("secret", path);
                    var timeout = jdWaybillConfig.GetJsonObjectIntValue("timeout", 5000);
                    var proxy = jdWaybillConfig.GetJsonObjectValue("proxy");

                    JArray skuList = null;
                    string sql2 = "SELECT SKU,Qty FROM O2O_SOFulfillPlanItem WHERE SubSONo=@OrderId";
                    var dbBase2 = Utils.GetDBAdapter("o2o", o2o, null, false);
                    var ds2 = dbBase2.Query(sql2, new List<DBParameter>() { new DBParameter("@OrderId", orderId) }, System.Data.CommandType.Text);
                    if (ds2 != null && ds2.Tables.Count > 0)
                        skuList = JSONHelper.DataTableToJArray(ds2.Tables[0]);

                    string j_address = bodyObject.GetJsonObjectStringValue("@j_address");
                    string j_contact = bodyObject.GetJsonObjectStringValue("@j_contact");
                    string j_tel = bodyObject.GetJsonObjectStringValue("@j_mobile");
                    string j_mobile = bodyObject.GetJsonObjectStringValue("@j_mobile");

                    string d_address = bodyObject.GetJsonObjectStringValue("@d_address");
                    string d_contact = bodyObject.GetJsonObjectStringValue("@d_contact");
                    string d_tel = bodyObject.GetJsonObjectStringValue("@d_tel");
                    string d_mobile = bodyObject.GetJsonObjectStringValue("@d_mobile");

                    string d_province = null;
                    string d_city = null;
                    string d_country = null;
                    string d_detailaddress = null;
                    string[] d_addresses = d_address.Trim().Split(' ');
                    if (d_addresses.Length > 3)
                    {
                        d_province = d_addresses[0];
                        d_city = d_addresses[1];
                        d_country = d_addresses[2];
                        d_detailaddress = d_address.Substring(d_province.Length + d_city.Length + d_country.Length);
                    }

                    var header = new Dictionary<string, string>() { { "Content-Type", "application/json" } };
                    JObject param = new JObject();
                    param["platformOrderNo"] = eSONo;
                    param["ePlatformCode"] = ePlatform;
                    param["eShopCode"] = eShopCode;
                    param["vendorOrderCode"] = orderId;
                    param["providerCode"] = courierCompCode;
                    param["skuList"] = skuList;
                    param["fromAddress"] = SplitAddress(j_address);
                    param["fromAddress"]["contact"] = j_contact;
                    param["fromAddress"]["phone"] = j_tel;
                    param["fromAddress"]["mobile"] = j_mobile;
                    param["toAddress"] = new JObject()
                    {
                        { "provinceName", d_province },
                        { "cityName", d_city },
                        { "countryName", d_country },
                        { "address",  d_detailaddress },
                        { "contact", d_contact },
                        { "phone", d_tel },
                        { "mobile", d_mobile }
                    };

                    string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    string requestString = param.ToString(Newtonsoft.Json.Formatting.None);
                    string sign = GetSign(appkey, timestamp, requestString, secret);

                    string responseString = HttpHelper.HttpWebRequest(string.Format("{0}?app_key={1}&timestamp={2}&sign={3}", url, appkey, timestamp, sign), requestString, "POST", timeout, header, proxy?.ToString());
                    var responseObject = JObject.Parse(responseString);
                    if (responseObject.GetJsonObjectStringValue("code") != "0000")
                        throw ExceptionHelper.GenException("@E120000006", responseObject.GetJsonObjectStringValue("msg"));

                    var waybillInfo = responseObject.GetJsonObject("waybillInfo");
                    var wayBillNo = waybillInfo.GetJsonObjectStringValue("wayBillNo");
                    var printData = waybillInfo.GetJsonObjectStringValue("perPrintData");
                    if (string.IsNullOrWhiteSpace(wayBillNo))
                        throw new BaseException("@E120000006", "Waybill No is empty");
                    if (string.IsNullOrWhiteSpace(printData))
                        throw new BaseException("@E120000006", "Waybill print data is empty");

                    path = string.Format("constant.jdCourierInfo.{0}", courierCompCode);
                    string printTempUrl = courierInfoConfig.GetNecessaryJsonObjectStringValue("printTempUrl", path);
                    string customPrintTempUrl = courierInfoConfig.GetJsonObjectStringValue("customPrintTempUrl");
                    var customPrintData = courierInfoConfig.GetJsonObjectValue("customPrintData");

                    JArray customData = null;
                    if (customPrintData != null)
                    {
                        var data = Utils.ParseObjectValues(customPrintData, dataset);
                        if (data != null && data.Type == JTokenType.Array)
                            customData = data as JArray;
                        else if (data != null && data.Type == JTokenType.Object)
                            customData = new JArray() { data as JObject };
                    }

                    var printDataResponse = GetWaybillData(printerService, printTempUrl, new JArray() { printData }, customPrintTempUrl, customData).Result;
                    var printDataResponseObject = JSONHelper.JsonToJObject(printDataResponse);
                    if (printDataResponseObject == null || printDataResponseObject.GetJsonObjectStringValue("success") != "true")
                        throw new BaseException("@E120000007", "");
                    var waybillBase64Data = printDataResponseObject.GetJsonObjectStringValue("content");
                    if (string.IsNullOrWhiteSpace(waybillBase64Data))
                        throw new BaseException("@E120000008", "");

                    var waybillFileName = wayBillNo + ".pdf";
                    GeneratePDF(waybillFileDir, waybillFileName, waybillBase64Data);

                    return string.Format("<?xml version='1.0' encoding='UTF-8'?><Response service=\"{0}\"><Head>OK</Head><Body><OrderResponse mailno=\"{1}\" filter_result=\"2\" orderid=\"{2}\" waybillfileurl=\"{3}\" /></Body></Response>", service, wayBillNo, orderId, waybillFileName);
                }
                else
                {
                    var courierInfo = constant.GetNecessaryJsonObject("courierInfo", "constant");
                    var courierInfoConfig = courierInfo.GetNecessaryJsonObject(courierCompCode, "constant.courierInfo");

                    string path = string.Format("constant.courierInfo.{0}", courierCompCode);
                    string url = courierInfoConfig.GetNecessaryJsonObjectStringValue("url", path);
                    string appkey = courierInfoConfig.GetNecessaryJsonObjectStringValue("appkey", path);
                    string secret = courierInfoConfig.GetNecessaryJsonObjectStringValue("secret", path);
                    var timeout = courierInfoConfig.GetJsonObjectIntValue("timeout", 5000);
                    var proxy = courierInfoConfig.GetJsonObjectValue("proxy");

                    xml = System.Text.RegularExpressions.Regex.Replace(xml, @"<Head>.+?</Head>", string.Format("<Head>{0}</Head>", appkey));
                    verifyCode = MD5ToBase64String(xml + secret);
                    string param = string.Format("xml={0}&verifyCode={1}", xml, verifyCode);

                    return HttpHelper.HttpWebRequest(url, param, "POST", timeout, null, proxy?.ToString());
                }
            }
            catch (BaseException ex)
            {
                if (!string.IsNullOrEmpty(ex.Message))
                    throw ex;
                throw new Exception(ex.Code);
            }
            catch (Exception ex)
            {
                LogHelper.Info("GetWaybillInfo fail: " + ex.Message);
                throw new Exception("@E120000001");
            }
        }

        private static async Task<string> GetWaybillData(string webSocketUrl, string tempUrl, JArray printData, string customTempUrl = null, JArray customData = null)
        {
            if (string.IsNullOrEmpty(webSocketUrl))
                throw new ArgumentNullException("webSocketUrl");
            if (string.IsNullOrEmpty(tempUrl))
                throw new ArgumentNullException("tempUrl");
            if (printData == null)
                throw new ArgumentNullException("printData");

            JObject sendMsg = new JObject();
            sendMsg["orderType"] = "PRE_View";
            sendMsg["parameters"] = new JObject();
            sendMsg["parameters"]["printName"] = "Microsoft Print to PDF";
            sendMsg["parameters"]["tempUrl"] = tempUrl;
            sendMsg["parameters"]["customTempUrl"] = customTempUrl ?? string.Empty;
            sendMsg["parameters"]["customData"] = customData ?? new JArray();
            sendMsg["parameters"]["printData"] = printData;

            var tokenSource = new CancellationTokenSource(10000);
            using (ClientWebSocket webSocket = new ClientWebSocket())
            {
                var receiveMsg = new StringBuilder();
                webSocket.ConnectAsync(new Uri(webSocketUrl), tokenSource.Token).Wait(tokenSource.Token);
                if (webSocket.State == WebSocketState.Open)
                {
                    byte[] bsend = Encoding.UTF8.GetBytes(sendMsg.ToString(Newtonsoft.Json.Formatting.None));
                    await webSocket.SendAsync(new ArraySegment<byte>(bsend), WebSocketMessageType.Text, true, CancellationToken.None);

                    ArraySegment<byte> receiveSegment = new ArraySegment<byte>(new byte[1024]);

                    WebSocketReceiveResult receiveResult = new WebSocketReceiveResult(0, WebSocketMessageType.Text, false);

                    while (!receiveResult.EndOfMessage)
                    {
                        receiveResult = webSocket.ReceiveAsync(receiveSegment, CancellationToken.None).Result;
                        receiveMsg.Append(Encoding.UTF8.GetString(receiveSegment.Array, 0, receiveResult.Count));
                    }

                    tokenSource = new CancellationTokenSource(10000);
                    webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "1", tokenSource.Token).Wait(tokenSource.Token);
                }

                return receiveMsg.ToString();
            }
        }

        private static string MD5ToBase64String(string str)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] MD5 = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str));//MD5(注意UTF8编码)
            string result = Convert.ToBase64String(MD5);//Base64
            return result;
        }
        
        private static JObject SplitAddress(string address)
        {
            if (address == null)
                address = string.Empty;

            string province = null;
            string city = null;
            string country = null;

            address = address.Trim();
            var matches1 = System.Text.RegularExpressions.Regex.Matches(address, @"^[^省]+省|[^自治区]+自治区|北京市|天津市|上海市|重庆市");
            if (matches1.Count > 0)
            {
                province = matches1[0].Groups[0].Value;
                address = address.Substring(province.Length);
            }
            else
            {
                province = address.Substring(0, 2);
                address = address.Substring(2);
            }

            address = address.Trim();
            var matches2 = System.Text.RegularExpressions.Regex.Matches(address, @"^[^市]+市|[^自治州]+自治州|[^盟]+盟");
            if (matches2.Count > 0)
            {
                city = matches2[0].Groups[0].Value;
                address = address.Substring(city.Length);
            }
            else
            {
                city = address.Substring(0, 2);
                address = address.Substring(2);
            }

            address = address.Trim();

            var matches3 = System.Text.RegularExpressions.Regex.Matches(address, @"^[^县]+县|[^旗]+旗|.+区");
            if (matches3.Count > 0)
            {
                country = matches3[0].Groups[0].Value;
                address = address.Substring(country.Length);
            }
            else
            {
                country = address.Substring(0, 2);
                address = address.Substring(2);
            }

            return new JObject()
            {
                { "provinceName", province },
                { "cityName", city },
                { "countryName", country },
                { "address", address }
            };
        }

        private void GeneratePDF(string filePath, string filename, string imageData)
        {
            if (!System.IO.Directory.Exists(filePath))
            {
                System.IO.Directory.CreateDirectory(filePath);
            }

            using (PdfDocument document = new PdfDocument())
            {

                // Create an empty page or load existing
                PdfPage page = document.AddPage();
                page.Size = PdfSharp.PageSize.A4;
                page.Orientation = PdfSharp.PageOrientation.Landscape;

                // Get an XGraphics object for drawing
                XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsUnit.Millimeter);

                byte[] arr = Convert.FromBase64String(imageData);
                var ms = new System.IO.MemoryStream(arr);
                using (XImage image = XImage.FromStream(ms))
                {
                    gfx.DrawImage(image, 21, 21, 100, 150);
                    gfx.DrawImage(image, 176, 21, 100, 150);
                    document.Save(filePath + "\\" + filename);
                }
                ms.Close();
            }
        }

        private string GetSign(string appkey, string timestamp, string requestString, string secret)
        {
            var headerCollection = new System.Collections.Specialized.NameValueCollection();
            headerCollection.Add("Content-Type", "application/json");
            
            var queryCollection = new System.Collections.Specialized.NameValueCollection();
            queryCollection.Add("app_key", appkey);
            queryCollection.Add("timestamp", timestamp);

            string sign = SpiUtils.GetSign(headerCollection, queryCollection, null, requestString, secret);

            return sign;
        }

        private class SpiUtils
        {
            private const string CTYPE_APP_JSON = "application/json";
            private const string SIGN = "sign";

            /// <summary>
            /// 校验SPI请求签名，不支持带上传文件的HTTP请求。
            /// </summary>
            /// <param name="header">header对象实例</param>
            /// <param name="query">url请求参数对象实例</param>
            /// <param name="request">HttpRequest对象实例</param>
            /// <param name="requestString">HttpRequest对象字符串</param>
            /// <param name="secret">APP密钥</param>
            /// <returns>签名字符串</returns>
            public static string GetSign(NameValueCollection header, NameValueCollection query, object request, string requestString, string secret)
            {
                try
                {
                    if (header == null || query == null || string.IsNullOrEmpty(secret))
                        throw new Exception();
                    string ctype = header["Content-Type"];
                    if (string.IsNullOrEmpty(ctype) || !ctype.StartsWith(CTYPE_APP_JSON))
                        throw new Exception();

                    string charset = GetRequestCharset(ctype);
                    IDictionary<string, string> parameters = new SortedDictionary<string, string>(StringComparer.Ordinal);

                    // 1. 获取url参数
                    Dictionary<string, string> queryMap = GetQueryMap(query);
                    AddAll(parameters, queryMap);

                    // 2. 生成签名并校验
                    string sign = Sign(parameters, requestString, secret, charset);

                    return sign;
                }
                catch
                {
                    return string.Empty;
                }
            }

            private static void AddAll(IDictionary<string, string> dest, IDictionary<string, string> from)
            {
                if (from != null && from.Count > 0)
                {
                    IEnumerator<KeyValuePair<string, string>> em = from.GetEnumerator();
                    while (em.MoveNext())
                    {
                        KeyValuePair<string, string> kvp = em.Current;
                        dest.Add(kvp.Key, kvp.Value);
                    }
                }
            }

            /// <summary>
            /// 签名规则：hex(md5(secret+sorted(header_params+url_params+form_params)+body)+secret)
            /// </summary>
            private static string Sign(IDictionary<string, string> parameters, string body, string secret, string charset)
            {
                IEnumerator<KeyValuePair<string, string>> em = parameters.GetEnumerator();

                // 第1步：把所有参数名和参数值串在一起
                StringBuilder query = new StringBuilder(secret);
                while (em.MoveNext())
                {
                    string key = em.Current.Key;
                    if (!SIGN.Equals(key))
                    {
                        string value = em.Current.Value;
                        query.Append(key).Append(value);
                    }
                }
                if (body != null)
                {
                    query.Append(body);
                }

                query.Append(secret);

                // 第2步：使用MD5加密
                MD5 md5 = MD5.Create();
                byte[] bytes = md5.ComputeHash(Encoding.GetEncoding(charset).GetBytes(query.ToString()));

                // 第3步：把二进制转化为大写的十六进制
                StringBuilder result = new StringBuilder();
                for (int i = 0; i < bytes.Length; i++)
                {
                    result.Append(bytes[i].ToString("X2"));
                }

                return result.ToString();
            }

            public static Dictionary<string, string> GetQueryMap(NameValueCollection query)
            {
                Dictionary<string, string> queryMap = new Dictionary<string, string>();
                if (query != null)
                {
                    foreach (var key in query.AllKeys)
                        queryMap[key] = query[key];
                }
                return queryMap;
            }

            public static string GetRequestCharset(string ctype = null)
            {
                string charset = "utf-8";
                if (!string.IsNullOrEmpty(ctype))
                {
                    string[] entires = ctype.Split(';');
                    foreach (string entry in entires)
                    {
                        string _entry = entry.Trim();
                        if (_entry.StartsWith("charset"))
                        {
                            string[] pair = _entry.Split('=');
                            if (pair.Length == 2)
                            {
                                if (!string.IsNullOrEmpty(pair[1]))
                                {
                                    charset = pair[1].Trim();
                                }
                            }
                            break;
                        }
                    }
                }
                return charset;
            }
        }
    }
}
